﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Linq;
using System.Data.Linq.SqlClient;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.ServiceModel;
using System.ServiceModel.Activation;
using jSLApp.Client.Workflow;
using jSLApp.Web.Workflow;
using jSLCore.Service.Security;
using jSLCore.Service.Workflow;
using jSLCore.Service.Workflow.Linq;
using Role = jSLApp.Web.Workflow.Role;
using User = jSLApp.Web.Workflow.User;

namespace jSLApp.Web
{
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class MainService
    {
        #region Helper

        private void LogEvent(DB db,LogType logType, string info, int? intId=null, Guid? guidId=null, User user=null)
        {
            var systemLog = new SystemLog {CreationDate = DateTime.Now,Info = info,RowGUIDId = guidId,RowIntId = intId,User1 = user,Type = (int) logType};

            db.SystemLogs.InsertOnSubmit(systemLog);
        }

        private void LogEvent(DB db, LogType logType, string info, int? intId, Guid? guidId, string user)
        {
            LogEvent(db, logType, info,intId,guidId,GetUser(db,user));
        }

        public static New GetNew(DB db,int id)
        {
            return db.News.GetOneOrjWorkflowException(id,"Новость",id.ToString());
        }

        private Message GetMessage(DB db, int messageId)
        {
            return db.Messages.GetOneOrjWorkflowException(messageId, "Сообщение", messageId.ToString());
        }

        private static void CheckPermission(Permission permission, string faultText)
        {
            SecurityHelper.CheckHasPermission((int)permission, faultText);
        }
        private void SendSystemMail(DB db, string what, params object[] pars)
        {
            var to = MainServiceEx.GetUsersToSendSystemMail(db);
            if (!string.IsNullOrEmpty(to))
                SendMail(to, true, what, pars);
        }

        private void SendMail(string to, string what, params object[] pars)
        {
            SendMail(to, false, what, pars);
        }
        private void SendMail(string to, bool _catch, string what, params object[] pars)
        {
            var props = ConfigurationManager.AppSettings;
            var client = new SmtpClient(props["SmtpServer"])
            {
                DeliveryMethod = SmtpDeliveryMethod.Network,
                Credentials = new NetworkCredential(props["SmtpLogin"], props["SmtpPwd"])
            };

            what = what + @"
С Уважением, Почтовая служба " + props["HostAddress"];

            try
            {
                client.Send(props["SmtpLogin"],
                    to,
                    "Noisy Makers",
                    string.Format(what, pars));
            }
            catch (SmtpFailedRecipientsException)
            {
                if (!_catch)
                    jWorkflowException.ThrowNew("Не удалось отправить письмо следующим получателям: "+to);
            }
        }
        
        private static User GetUser(DB db, string userLogin)
        {
            return db.Users.GetOneOrjWorkflowException(userLogin, "Пользователь", userLogin, "Login");
        }



       
        private string GetUserNameOrWorkflowException(string errorText)
        {
            var userName = SecurityHelper.GetUserName();

            if (string.IsNullOrEmpty(userName)) jWorkflowException.ThrowNew(errorText);
            return userName;
        }

        private string GetUserNameOrExc()
        {
            var userName = SecurityHelper.GetUserName();

            if (string.IsNullOrEmpty(userName)) throw new InvalidOperationException();
            return userName;
        }

        public Commentable CreateNewCommentable(User user,string name)
        {
            var commentable = new Commentable()
            {
                User = user,
                Name = name,
                CreationDate = DateTime.Now
            };
            return commentable;
        }
        #endregion

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SpamThem()
        {
            using (var db = new DB())
            {
                string lg = "Начата рассылка уведомлений. ";

                var user = db
                    .Users
                    .OnlyValidMail()
                    .Where(u=>u.IsEnabled)
                    .OrderBy(u=>u.LastMail)
                    .FirstOrDefault();

                if(user!=null)
                {var props = ConfigurationManager.AppSettings;
                    lg += "Выбран пользователь: " + user.Login.Trim();
                    var dt = user.LastMail ?? user.LastVisit ?? user.CreationDate;
                    user.LastMail = DateTime.Now;
                    user.UnSubscribeCode = Guid.NewGuid();

                    string mailText = string.Empty;
                    //Надо проверить что делать, если dt = null
                    //Checking News
                    if(user.MailingNews)
                    {
                        var newNews = db.News.Where(n=>n.Type==(int) ArticleType.New).Where(n=>n.Commentable.CreationDate>dt);
                        if(newNews.Any())
                        {
                            string strNews = @"Новости на сайте:
";
                            var ns = newNews.BeforeSending().ToArray();
                            foreach (var newNew in ns)
                            {
                                strNews += newNew.Header + @"
";

                            }

                            mailText += strNews+@"

";
                            //strNews += @"Для просмотра перейдите на сайт в раздел ""Статьи и новости""\n";
                        }
                    }
                    if(user.MailingInbox)
                    {
                        var messages = user.Inbox.Where(m=>m.CreationDate>dt).BeforeSending().ToArray();

                        if(messages.Any())
                        {
                            string str = @"Есть новые сообщения от пользователей:
";
                            foreach (var message in messages)
                            {
                                str += message.FromText + " " + message.CreationDate.ToString("dd.MM.yyyy hh:mm") + @"
";
                            }
                            mailText += str+@"

";
                            // str+=@"Для просмотра почты перейдите на сайт в раздел ""Почта""\n";
                        }
                    }

                    if(user.MailingComments)
                    {
                        var commentables = user.Commentables.Where(c=>c.Comments.Any(a=>a.CreationDate>dt))
                            .BeforeSending()
                            .ToArray();

                        if (commentables.Any())
                        {
                            var str = @"На ваши посты есть новые комментарии:
";
                            foreach (var commentable in commentables)
                            {
                                str += commentable.Name + @"
";
                            }

                            mailText += str+@"

";
                        }
                    }

                    var subsriptions = user.Subsriptions.Where(s=>s.Commentable.Comments.Any(c=>c.CreationDate>dt)).BeforeSending().ToArray();
                    if(subsriptions.Any())
                    {
                        var str = @"Посты, на которые вы подписаны содержат новые комментарии:
";
                        foreach (var subsription in subsriptions)
                        {
                            string paramname = "";
                            var commentableType = subsription.Commentable.GetCommentableType();

                            switch (commentableType)
                            {
                                    case CommentableType.Music:
                                    paramname = PageNames.MusicDetail;break;
                                    case CommentableType.NewsAcrtible:
                                    paramname = PageNames.ArticleDetail;
                                        break;
                                    case CommentableType.Topic:
                                    paramname = PageNames.TopicDetail;
                                        break;
                            }

                            var link = string.Format("{0}/{1}?{2}={3}", props["HostAddress"], props["PageAddress"],
                                                     paramname, subsription.To);

                            string info = subsription.NameTxt + " " + link+"\n";

                            str += info;
                        }

                        mailText += str+@"

";
                    }

                    var disclamer =
                        string.Format(
                            @"Если вы не хотите более получать писем с нашего сайта - пожалуйста, пройдите по ссылке: {0}/{1}?{2}={3}
",
                            props["HostAddress"], props["PageAddress"], PageNames.UnsubsribeAll, user.UnSubscribeCode);

                   

                    if(!string.IsNullOrEmpty(mailText))
                    {
                        lg += "Пользователю отправляется сообщение.";

                        mailText = @"Здравствуйте.
На сайте Noisy Makers есть обновления


"+mailText;
                        mailText += disclamer;

                        try
                        {
                            LogEvent(db, LogType.System, lg);
                            SendMail(user.email, mailText);
                        }
                        catch (Exception ex)
                        {
                            LogEvent(db,LogType.Error,ex.ToString() );
                        }
                        
                    }
                }

                 db.SubmitChanges();

             
            }
        }


        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void UnSubsribeAll(Guid code)
        {
            using (var db = new DB())
            {
                var user = db.Users.SingleOrDefault(u=>u.UnSubscribeCode==code);

                if(user==null)jWorkflowException.ThrowNew("Не найден указанный код. Возможно код устарел");

                user.MailingComments = false;
                user.MailingInbox = false;
                user.MailingNews = false;

                DeleteAllSubscriptions(db,user.Login);

                LogEvent(db,LogType.UserAction, "Пользователь отписывается от всего",null,null,user);

                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Topic GetTopic(int topicId)
        {
            var userName = GetUserNameOrExc();
            using (var db = new DB())
            {
                var topic = db.Topics.GetOneOrjWorkflowException(topicId,"Топик",string.Empty);
                topic.BeforeSending();
                return topic;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeleteTopic(int topicId)
        {
            CheckPermission(Permission.Users, "Нет прав на удаление топика");
            using (var db = new DB())
            {
                var topic = db.Topics.GetOneOrjWorkflowException(topicId, "Топик", string.Empty);
                db.Commentables.DeleteOnSubmit(topic.Commentable);

                LogEvent(db,LogType.UserAction, "Пользователь удаляет топик.",topicId,null,GetUserNameOrExc());

                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetDone(int musicId)
        {
            CheckPermission(Permission.SetTrackIsDone, "Нет права помечать трек, как готовый");
            using (var db = new DB())
            {
                var music = db.Musics.GetOneMusic(musicId);

                if(!music.IsPublic)
                    jWorkflowException.ThrowNew(@"Невозможно выполнить операцию, так как для трека не установлен флаг ""Публичный"". Внимание! Установка флага ""Публичный"" повлечет за собой появление его на форуме");

                music.IsDone = true;

                LogEvent(db,LogType.UserAction, "Пользователь устанавливает готовность трека",musicId,null,GetUserNameOrExc());
                db.SubmitChanges();
            }
        }

        #region Comments
        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Comment[] GetComments(int entityId)
        {
            GetUserNameOrExc();
            using (var db = new DB())
            {
                Commentable commentable = GetCommentable(db, entityId);
                return commentable
                    .Comments
                    .BeforeSending()
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeleteComment(int commentId)
        {
            CheckPermission(Permission.AlienTrackChange, "Не хватает прав для удаления комментария");
            using (var db = new DB())
            {
                var comment = db.Comments.GetOneOrjWorkflowException(commentId,"Комментарий","");

                comment.IsDeleted = true;

                LogEvent(db,LogType.UserAction, "Пользователь удаляет комментарий (Указан идентификатор топика)",comment.Commentable,null,GetUserNameOrExc());

                db.SubmitChanges();
            }
        }

        private Commentable GetCommentable(DB db, int entityId)
        {
            return db.Commentables.GetOneOrjWorkflowException(entityId, "Комментируемый объект", entityId.ToString());
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void CreateComment(int entityId,string xamlText,Rate rating)
        {
            var userName = GetUserNameOrExc();
            using (var db = new DB())
            {
                var user = GetUser(db,userName);
                var commentable = GetCommentable(db,entityId);

                var comment = new Comment {Commentable1 = commentable, CreationDate = DateTime.Now, Text = xamlText,User =  user};

                if(rating!=null)
                {
                    var music = db.Musics.GetOneMusic(entityId);

                    var rate = new Rate
                                   {
                                       RateDance = rating.RateDance,
                                       RateMusic = rating.RateMusic,
                                       RateQuality = rating.RateQuality,
                                       Comment = comment
                                   };
                    music.RateMusic(rate);
                }

                db.SubmitChanges();
            }
        }

        #endregion

        #region Article

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public int CreateArticle(string brief, string header, byte[] image, ArticleType type, byte[] file,string ext)
        {
            CheckPermission(Permission.Users, "Для создания статьи необходимо обладать правами администратора");
            using (var db = new DB())
            {

                var commentable = CreateNewCommentable(GetUser(db, SecurityHelper.GetUserName()), header);

                var ent = new New
                {
                    Commentable = commentable,
                    BodyURL = "will be set later",
                    Brief = brief,
                    HeaderImage = new Binary(image),
                    Type = (int)type,
                };

                db.News.InsertOnSubmit(ent);
                db.SubmitChanges();
                db.Refresh(RefreshMode.OverwriteCurrentValues, ent);

                var fileName = "article_N" + ent.id + "."+ ext;

                var stream = File.Create(Path.Combine(ServerHelper.CurrentWebApplicationPhysycalPath, fileName), file.Length);
                stream.Write(file, 0, file.Length);
                stream.Close();

                ent.BodyURL = fileName;

                LogEvent(db,LogType.UserAction, "Пользователь создал статью: "+header,null,null,GetUserNameOrExc());
                db.SubmitChanges();
                return ent.id;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public New GetArticle(int id)
        {
            using (var db = new DB())
            {
                var ent = GetNew(db, id);
                ent.BeforeSending();
                return ent;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public New[] SearchArticles(string searchText)
        {
            using (var db = new DB())
            {
                return SearchArticles(db, searchText);
            }
        }

        internal static New[] SearchArticles(DB db, string searchText="")
        {
            return db.News
                .Where(n => n.Commentable.Name.Contains(searchText) || n.Brief.Contains(searchText))
                .BeforeSending()
                .ToArray();
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeleteArticle(int articleId)
        {
            CheckPermission(Permission.Users, "Удаление статьи");
            using (var db = new DB())
            {
                var ent = db.News.GetOneOrjWorkflowException(articleId,"Статья",string.Empty);

                var path = Path.Combine(ServerHelper.CurrentWebApplicationPhysycalPath, ent.BodyURL);
                File.Delete(path);

                db.News.DeleteOnSubmit(ent);
                db.Commentables.DeleteOnSubmit(ent.Commentable);
                db.SubmitChanges();
            }
        }

        #endregion

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SendMailToUser(string login,string message)
        {
            string userName = GetUserNameOrWorkflowException("Анонимный пользователь не может посылать письма другим пользователям");
            using (var db = new DB())
            {
                var user = GetUser(db, login);
                var sendUser = GetUser(db, userName);

                string res =
                    string.Format(
                        @"Вам отправлено письмо от пользователя {0}({2}):

{1}

Вы можете ответить на адрес {3}",
                        userName, message, sendUser.Name, sendUser.email);

                SendMail(user.email,res);
            }
        }


        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public bool CanRate(int _musicId)
        {
            var userName = GetUserNameOrExc();
            using (var db = new DB())
            {
                var user = GetUser(db,userName);

                var music = db.Musics.GetOneMusic(_musicId);
                return !music.Commentable.Comments.Where(c => c.User == user).Any(com => com.Rate != null);
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetDontShowChat(bool show)
        {
            var userName = GetUserNameOrExc();
            using (var db = new DB())
            {
                var user = GetUser(db,userName);

                user.dontShowChat = show;
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public bool GetDontShowChat()
        {
            string userName;
            
            try
            {
                userName = GetUserNameOrExc();
            }
            catch(InvalidOperationException)
            {
                return false;
            }
            
            using (var db = new DB())
            {
                var user = GetUser(db, userName);
                return user.dontShowChat;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void ChangeMailSettings(string login,bool newsMail,bool messageMail,bool commentMail)
        {
            string userName = GetUserNameOrExc();
            using (var db = new DB())
            {   
                if(userName.Trim()!=login.Trim())CheckPermission(Permission.ChangeUserEx, "Нет прав");

                var user = GetUser(db, login);

                user.MailingInbox = messageMail;
                user.MailingNews = newsMail;
                user.MailingComments = commentMail;
                db.SubmitChanges();

                LogEvent(db,LogType.UserAction, "Пользователь изменил параметры уведомлений",null,null,GetUserNameOrExc());
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void ManualAcceptMail(string login)
        {
            CheckPermission(Permission.Users, "Необходимы права администратора");
            using(var db = new DB())
            {
                var user = GetUser(db,login);
                user.emailAccepted = true;
                user.IsEnabled = true;

                LogEvent(db,LogType.UserAction, "Пользователь разблокирован: "+login.Trim(),null,null,GetUserNameOrExc());

                db.SubmitChanges();

                SendMail(user.email,true,"Ваш e-mail подтвержден и аккаунт разблокирован");
            }
        }


        #region Messages
        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Message[] GetUserMessages(string login, string searchText, bool inOrOutBox /*true - in, false - out*/)
        {
            var userName = GetUserNameOrExc();

            if (userName != login) CheckPermission(Permission.ChangeUserEx, "Невозможно получить сообщения другого пользователя");

            using (var db = new DB())
            {
                var user = GetUser(db, userName);

                var msgs = inOrOutBox ? user.Inbox : user.Outbox;

                var messages = msgs.Where(m => m.Text.Contains(searchText));
                return messages.BeforeSending().ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Message GetUserMessage(int messageId)
        {
            var userName = GetUserNameOrExc();

            using (var db = new DB())
            {
                var message = GetMessage(db, messageId);

                if (message.From.Trim() != userName && message.To.Trim() != userName) CheckPermission(Permission.ChangeUserEx, "Вы не может получить это письмо, так как не являетесь ни получателем, ни отправителем");

                if(message.To.Trim()==userName)//Recepient receives the message
                        message.IsReaded = true;
                db.SubmitChanges();
                message.BeforeSending();
                return message;
            }
        }


        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SendMessage(string to, string text)
        {
            var userName = GetUserNameOrExc();

            using (var db = new DB())
            {
                var sender = GetUser(db, userName);
                var toUser = GetUser(db, to);

                var message = new Message { Sender = sender, Recepient = toUser, Text = text,CreationDate = DateTime.Now};
                db.Messages.InsertOnSubmit(message);
                db.SubmitChanges();
            }
        } 
        #endregion

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public User[] GetUserListSimple(string searchText)
        {
            using (var db = new DB())
            {
                IQueryable<User> users = db.Users;
                if (!string.IsNullOrEmpty(searchText))
                    users = users.Where(u => u.Name.Contains(searchText));
                return users.BeforeSending().ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void EncreaseListenCount(int id)
        {
            using (var db = new DB())
            {
                try
                {
                    var music = db.Musics.GetOneMusic(id);
                    music.ListenCount = music.ListenCount+1;
                    db.SubmitChanges();
                
                }
                catch (Exception ex)
                {
                    //its silent
                    LogEvent(db ,LogType.Error, ex.ToString(),id);
                }
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void EncreaseDownloadCount(int id)
        {
            using (var db = new DB())
            {
                try
                {
                    var music = db.Musics.GetOneMusic(id);
                    music.DownloadCount++;
                    db.SubmitChanges();
                }
                catch (Exception ex)
                {
                    //its silent
                    LogEvent(db, LogType.Error, ex.ToString(),id);
                }
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void ChangeAllowDownload(int id,bool allowDownload)
        {
            using (var db = new DB())
            {
                     var music = db.Musics.GetOneMusic(id);
                    music.AllowDownload = allowDownload;

                LogEvent(db,LogType.UserAction, "Изменены пара-ры разрещений скачивания: " + (allowDownload?"Разрешено":"Запрещено"),id,null,GetUserNameOrExc());

                    db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public User[] SearchUser(string searchText,bool showUnaccepted)
        {
            CheckPermission(Permission.Users, "Нет прав для работы с пользователями");
            using (var db = new DB())
            {
                //if one user is found, and it's blocked
                var badUser = db.Users.SingleOrDefault(u=>u.Login==searchText && !u.emailAccepted);
                if(badUser!=null)
                {
                    jWorkflowException.ThrowNew("Найден один пользователь с указанным логином, но его e-mail не подтвержден: " + badUser.email);
                }

                var result = db.Users.AsQueryable();
                if(!showUnaccepted)result = result.OnlyValidMail();
                if (!string.IsNullOrEmpty(searchText))
                {
                    result = result.BySearchText(searchText);
                }

                return result
                    .BeforeSending()
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Music[] SearchMusicList(string searchText)
        {
            using (var db = new DB())
            {
                return SearchMusicList(db, searchText);
            }
        }

        internal static Music[] SearchMusicList(DB db)
        {
            return SearchMusicList(db,string.Empty);
        }

        private static Music[] SearchMusicList(DB db, string searchText)
        {
            var publ = db.Musics./*OnlyPublicAndOfUser*/Where(m=>m.IsPublic && m.IsDone);//(SecurityHelper.GetUserName();
            var strings = searchText.Split(' ');

            publ = strings.Aggregate(publ, (current, str) => current.BySearchText(str));
            return publ.BeforeSending().ToArray();
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Music[] GetUserMusicList(string author)
        {

            using (var db = new DB())
            {
                var user = GetUser(db,author);

                var publ = user
                    .Commentables
                    .AsQueryable()
                    .OnlyMusic()
                    .OnlyPublicAndOfUser(SecurityHelper.GetUserName());
                return publ.BeforeSending().ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SubmitEMail(Guid code)
        {
            using (var db = new DB())
            {
                var user = db.Users.SingleOrDefault(u=>u.VerifyCode==code);
                if(user==null)jWorkflowException.ThrowNew("Пользователь не найден. Возможно вы слишком поздно подтвердили вашу заявку на регистрацию. Попытайтесь повторить процедуру регистрации");
                
                if (!user.emailAccepted)//auto unlock, but only once,so disabled users can't unlock by submiting email again
                {
                    user.IsEnabled = true;
                }
                user.emailAccepted = true;
                
                LogEvent(db,LogType.UserAction, "Пользователь подтвердил адрес электронной почты",null,null,user);

                db.SubmitChanges();

                /*SendSystemMail(db, "Пользователь {0} подтвердил свой адрес электронной почты:{1}", user.Login.Trim(),
                               user.email);*/
            }
        }

        

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SaveAllRoles(Role[] roles)
        {
            CheckPermission(Permission.ChangeUserEx, "Необходимы права супер-пользователь");
            using(var db = new DB())
            {
                foreach (var role in roles)
                {
                    var one = db.Roles.GetOneOrjWorkflowException(role.id,"Роль",role.Name);
                    one.AdministratorAccess = role.AdministratorAccess;
                }

                LogEvent(db,LogType.UserAction, "Изменены пар-ры доступа для администратора",null,null,GetUserNameOrExc());

                db.SubmitChanges();
            }
            
        }
        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Role[] GetAllRoles()
        {
            CheckPermission(Permission.ChangeUserEx, "Необходимы права супер-пользователь");
            using (var db = new DB())
            {
                return db.Roles
                    .BeforeSending()
                    .ToArray();
            }
        }
        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Role[] GetAdministratorRoleAccess()
        {
            CheckPermission(Permission.Users, "Нет прав администрирования");
            using (var db = new DB())
            {
                return db.Roles
                    .Where(r => r.AdministratorAccess)
                    .BeforeSending()
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void Registration(string login,string pwd,string mail)
        {
            using (var db = new DB())
            {
                if (db.Users.Any(u => u.Login == login )) jWorkflowException.ThrowNew("Пользователь с таким логином уже существует");
                if (db.Users.OnlyValidMail().Any(u => SqlMethods.Like(u.email, mail))) jWorkflowException.ThrowNew("Пользователь с таким e-mail уже существует");
                var verCode = Guid.NewGuid();
                var defaultRole = db.Roles.GetOne(new Guid("41e5661e-3c6c-4099-876c-2074dd99e268"));//dirty hack
                var user = new User
                               {
                                   email = mail,
                                   CreationDate = DateTime.Now,
                                   Login = login,
                                   Password = new Binary(SecurityHelper.GetPasswordHash(pwd)),
                                   VerifyCode = verCode,
                                   Role = defaultRole,
                                   dontShowChat = false,
                                   MailingComments = true,
                                   MailingInbox = true,
                                   MailingNews = true
                               };

                var props = ConfigurationManager.AppSettings;
#if !DEBUG
                SendMail(mail,@"Здравствуйте.
Ваш адрес был указан в качестве электронной почты для регистрации 
на сайте {0}.
Для подтверждения регистрации выделите ссылку ниже и вставьте ее в ваш web браузер.
{1}/{2}?{4}={3}
Если вы не регистрировались на сайте - просто проигнорируйте сообщение.", props["HostAddress"], props["HostAddress"], props["PageAddress"], verCode,PageNames.EmailVerificationCode);
#endif
                db.Users.InsertOnSubmit(user);

                LogEvent(db, LogType.System, string.Format("Пользователь зарегистрировался: {0} Логин:{1}", user.email, user.Login.Trim()));

                db.SubmitChanges();

                //SendSystemMail(db, "Пользователь зарегистрировался: {0} Логин:{1}",user.email,user.Login.Trim());
            }
        }
    
        [OperationContract]
        public string GetCurrentAuthorName()
        {
            using (var db = new DB())
            {
                var userName = GetUserNameOrExc();
                var user = db.Users.GetOneOrjWorkflowException(userName, "Пользователь", userName, "Login");
                return user.Name;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public byte[] GetUserImage(string userLogin)
        {
            using (var db = new DB())
            {
                User user = GetUser(db, userLogin);
                if (user.Image != null) return user.Image.ToArray();
                return GetNoPhotoImage();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public int CreateTopic(string theme,string message)
        {
            var userName = GetUserNameOrExc();
            using (var db = new DB())
            {
                var user = GetUser(db,userName);
                var commentable = CreateNewCommentable(user,theme);
                var topic = new Topic()
                                {Commentable =  commentable};

                var comment = new Comment()
                                  {
                                      CreationDate = DateTime.Now,
                                      Commentable1 = commentable,
                                      IsDeleted = false,
                                      Text =  message,
                                      User = user,
                                  };

                db.Topics.InsertOnSubmit(topic);
                db.Comments.InsertOnSubmit(comment);

                LogEvent(db,LogType.UserAction, "Создан топик в форуме: "+ theme,null,null,user);

                db.SubmitChanges();
                db.Refresh(RefreshMode.OverwriteCurrentValues,commentable);
                return commentable.id;
            }
        }

        #region chat
        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void CreateChat(string message)
        {
            var userName = GetUserNameOrExc();
            using (var db = new DB())
            {
                var user = GetUser(db, userName);

                new Chat { CreationDate = DateTime.Now, User = user, Text = message };
                db.SubmitChanges();
            }
        }



        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Chat[] GetChat(DateTime lastCall)
        {
            try
            {
                GetUserNameOrExc();
            }
            catch(InvalidOperationException)
            {
                return new Chat[0];
            }
            using (var db = new DB())
            {
                return db
                    .Chats
                    .Where(c => c.CreationDate > lastCall)
                    .OrderBy(c => c.CreationDate)
                    .BeforeSending()
                    .ToArray();
            }
        } 
        #endregion

        #region subscription
        
        private Subsription[] GetUserSubscriptions(string userName)
        {
            using (var db = new DB())
            {
                var user = GetUser(db,userName);

                return user
                    .Subsriptions
                    .BeforeSending()
                    .ToArray();
            }
        }


        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Subsription[] GetMySubscriptions()
        {
            string userName = GetUserNameOrExc();
            return GetUserSubscriptions(userName);
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeleteMySubscriptions()
        {
            var userName = GetUserNameOrExc();
            using (var db = new DB())
            {
                DeleteAllSubscriptions(db, userName);
                db.SubmitChanges();
            }
        }

        private void DeleteAllSubscriptions(DB db, string userName)
        {
            var user = GetUser(db,userName);

            db.Subsriptions.DeleteAllOnSubmit(user.Subsriptions);
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public bool IsSubscripted(int commentableId)
        {
            var userName = GetUserNameOrExc();
            using (var db = new DB())
            {
                var user = GetUser(db,userName);
                var commentable = GetCommentable(db,commentableId);

                return user.Subsriptions.Any(s=>s.Commentable==commentable);
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void CreateSubscription(int commentableId)
        {
            var userName = GetUserNameOrExc();
            using (var db = new DB())
            {
                var user = GetUser(db,userName);

                var commentable = GetCommentable(db,commentableId);

                new Subsription {Commentable = commentable, User = user};
                    db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeleteSubscription(int subscriptionId)
        {
            var userName = GetUserNameOrExc();

            using (var db = new DB())
            {
                var user = GetUser(db,userName);

                var subsription = db.Subsriptions.GetOneOrjWorkflowException(subscriptionId,"Подписка",string.Empty);
                
                if(subsription.User!=user)CheckPermission(Permission.ChangeUserEx, "Нельзя удалять чужую подписку");

                db.Subsriptions.DeleteOnSubmit(subsription);
                db.SubmitChanges();
            }
        }

        #endregion

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Commentable[] SearchForum(string sText)
        {
            using (var db = new DB())
            {
                return db.Commentables
                    .OnlyMusicOrPublicMusic()
                    .Search(sText)
                    .OrderByDescending(c=>c.Comments.FirstOrDefault().CreationDate)
                    .Take(200)
                    .BeforeSending()
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeleteMusic(int musicId)
        {
            var userName = GetUserNameOrExc();

            using (var db = new DB())
            {
                var music = db.Musics.GetOneMusic(musicId);
                var user = GetUser(db,userName);

                if(music.Commentable.User!=user)
                    CheckPermission(Permission.AlienTrackDelete, "Вы не имеете права удалять файлы других пользователей");

                File.Delete(Path.Combine(ServerHelper.CurrentWebApplicationPhysycalPath, music.FullFileName));

                
                db.Commentables.DeleteOnSubmit(music.Commentable);

                LogEvent(db,LogType.UserAction, "Удален трек: "+music.Commentable.Name,null,null,user);

                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void ChangeMusicName(int musicId,string newName)
        {
            var userName = GetUserNameOrExc();

            using (var db = new DB())
            {
                var music = db.Musics.GetOneMusic(musicId);

                var user = GetUser(db, userName);

                if (music.Commentable.User != user)
                    CheckPermission(Permission.AlienTrackChange, "Вы не имеете права изменять публикации других пользователей");

                music.Commentable.Name = newName;
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void ChangeCurrentUserName(string newName)
        {
            var userName = GetUserNameOrExc();

            using (var db = new DB())
            {
                var user = db.Users.GetOneOrjWorkflowException(userName, "Пользователь", userName, "Login");
                user.Name = newName;

                LogEvent(db,LogType.UserAction, "Пользователь изменил свое имя: "+newName,null,null,userName);

                db.SubmitChanges();
            }
        }
         

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void ChangePictureForCurrentUser(byte[] bs)
        {
            var userName = GetUserNameOrExc();

            using (var db = new DB())
            {
                var user = db.Users.GetOneOrjWorkflowException(userName, "Пользователь", userName, "Login");


                try
                {
                    user.Image = new Binary(bs.ConvertImageToPng(250, 250));
                }
                catch (Exception)
                {

                    jWorkflowException.ThrowNew("Не удалось прочесть файл изображения");
                }
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void ChangeMusicInfo(int musicId,string newInfo)
        {
            var userName = GetUserNameOrExc();

            
            using (var db = new DB())
            {
                var music = db.Musics.GetOneMusic(musicId);
                var user = GetUser(db, userName);

                if (music.Commentable.User != user)
                    CheckPermission(Permission.AlienTrackChange, "Вы не имеете права изменять публикации других пользователей");

                music.Info = newInfo;
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void ChangeMusicPicture(int musicId,byte[] bs)
        {
            var userName = GetUserNameOrExc();

            using (var db = new DB())
            {
                var music = db.Musics.GetOneMusic(musicId);
                var user = GetUser(db, userName);

                if (music.Commentable.User != user)
                    CheckPermission(Permission.AlienTrackChange, "Вы не имеете права изменять публикации других пользователей");

                try
                {
                   music.Image = new Binary(bs.ConvertImageToPng(250, 250));

                }
                catch (Exception)
                {
                    jWorkflowException.ThrowNew("Не удалось прочесть файл изображения");
                }
                db.SubmitChanges();
                
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void Visit()
        {
            var userName = GetUserNameOrExc();
            using (var db = new DB())
            {
                var user = GetUser(db, userName);

                user.LastVisit = DateTime.Now;

                LogEvent(db,LogType.UserAction, "Пользователь посетил сайт",null,null,user);
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public int AddMusic(string songName,byte[] music,double durationIsSeconds)
        {CheckPermission(Permission.UploadMusic, "Вы не имеет права загружать файлы");
        var userName = GetUserNameOrExc();

            using (var db = new DB())
            {
                var user = db.Users.GetOne(userName, "Login");

                var com = CreateNewCommentable(user, songName);
                    
                var mus = new Music
                                {
                                    Commentable =  com,
                                    Length = durationIsSeconds,
                                    FullFileName = "dont use it",// will be provided later
                                };
                    
                    
                //todo: mus.Format сейчас не умеем определять
                //todo: mus.Quality пока что не определяется

                db.SubmitChanges();

                db.Refresh(RefreshMode.OverwriteCurrentValues,mus);

                var fileName = "soundFile_N" + mus.id+".mp3";

                var stream = File.Create(Path.Combine(ServerHelper.CurrentWebApplicationPhysycalPath, fileName), music.Length);
                stream.Write(music, 0, music.Length);
                stream.Close();

                mus.FullFileName = fileName;
                db.SubmitChanges();
                
                //SendSystemMail(db, "Пользователь загрузил композицию: {0} Логин:{1}", songName, user.Login.Trim());
                LogEvent(db,LogType.UserAction, string.Format("Пользователь загрузил композицию: {0} Логин:{1}", songName, user.Login.Trim()));
                   
                return mus.id;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public SystemLog[] GetLogs()
        {
            CheckPermission(Permission.ChangeUserEx, "У вас нет прав просматривать журнал событий");

            using (var db = new DB())
            {
                return db.SystemLogs.OrderByDescending(lg=>lg.CreationDate).Take(200).BeforeSending().ToArray();
            }
        }

         [FaultContract(typeof(jWorkflowException))]
         [OperationContract]
        public byte[] GetMusicFile(int musicId)
         {
             using (var db = new DB())
             {
                 var music = db.Musics.GetOneMusic(musicId);

                 if(!music.AllowDownload)
                 {
                     var userName = SecurityHelper.GetUserName();
                     if (string.IsNullOrEmpty(userName) && music.Commentable.Creator != userName)
                         if(!SecurityHelper.IsUserHasPermission(userName,(int) Permission.ChangeUserEx))
                     {

                         jWorkflowException.ThrowNew("Этот трек запрещен для скачивания");
                     }
                 }

                 var fileName = music.FullFileName;

                 var stream = File.OpenRead(Path.Combine(ServerHelper.CurrentWebApplicationPhysycalPath, fileName));
                 var bs = new byte[stream.Length];
                 stream.Read(bs, 0, (int) stream.Length);
                 stream.Close();

                 music.DownloadCount++;
                 db.SubmitChanges();
                 return bs;
             }
         }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Music GetMusic(int id)
        {
            using (var db = new DB())
            {
                var music = db.Musics.GetOneMusic(id);

                music.PrepareBigSending(GetNoPhotoImage());
                return music;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SaveIsPublic(int musicId,bool isPublic)
        {
            CheckPermission(Permission.IsPublicChanging,
                                  "Вы не имеете права изменять публикации других пользователей");
            var userName = GetUserNameOrExc();

            using (var db = new DB())
            {
                var music = db.Musics.GetOneMusic(musicId);

                var user = GetUser(db, userName);

                if (music.Commentable.User != user)
                {
                    CheckPermission(Permission.AlienTrackChange,
                                    "Вы не имеете права изменять публикации других пользователей");
                    
                }
                
                music.IsPublic = isPublic;

                LogEvent(db,LogType.UserAction, "Изменены пар-ры публикации",musicId,null,user);

                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public User GetUser(string login)
        {
            CheckPermission(Permission.Users, "Нет прав для работы с пользователями");
            using (var db = new DB())
            {
                var user = GetUser(db,login);

                user.BeforeSending();
                return user;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public User GetProject(string login)
        {
            using (var db= new DB())
            {
                var user = GetUser(db, login);

                user.PrepareProject();
                return user;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetIsEnabled(string login,bool isEnabled)
        {
            CheckPermission(Permission.Users, "Нет прав для работы с пользователями");
            using (var db = new DB())
            {
                var user = GetUser(db, login);
                if(user.IsSuperVisor()
                   ||
                   user.IsAdministrator())
                {
                    CheckPermission(Permission.ChangeUserEx, "Нет прав для изменения параметров этой группы пользователей");
                }
                user.IsEnabled = isEnabled;
                if(isEnabled)
                {
#if !DEBUG
                    SendMail(user.email, @"Ваш аккаунт {0} активизирован.", login.Trim());
#endif
                }

                LogEvent(db,LogType.UserAction, "Активизация аккаунта: "+login.Trim(),null,null,user);

                db.SubmitChanges();
            }
        }


        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SubmitPasswordChange(Guid pwdCode,string newPwd)
        {
            using (var db = new DB())
            {
                var user = db.Users.SingleOrDefault(u=>u.ChangePasswordCode==pwdCode);
                if(user==null)jWorkflowException.ThrowNew("Неверный проверочный код для смены пароля. Возможно вы уже использовали его.");

                user.ChangePasswordCode = null;
                user.Password = new Binary(SecurityHelper.GetPasswordHash(newPwd));

                LogEvent(db,LogType.UserAction, "Изменения пароля пользователя",null,null,user);
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void ResetPassword(string login,string mail)
        {
            
            using (var db = new DB())
            {
                var user = GetUser(db, login);
                if(user.email.Trim().ToUpper()!=mail.Trim().ToUpper())
                {
                    jWorkflowException.ThrowNew("Не найден пользователь с такими логин и Email");
                }
                if(!user.emailAccepted)
                {
                    jWorkflowException.ThrowNew("Ваш email не подтвержден после регистрации.");
                }

                var pwdCode = Guid.NewGuid();
                user.ChangePasswordCode = pwdCode;

                LogEvent(db,LogType.UserAction, "Пользователь отправил запрос на изменения пароля",null,null,user);
                db.SubmitChanges();

                                   var props = ConfigurationManager.AppSettings;

                SendMail(user.email,@"Кто-то послал запрос на изменение вашего пароля.
Для изменения пароля перейдите по ссылке ниже
{0}/{1}?{3}={2}
Если вы не посылали запрос за изменения пароля - просто проигнорируйте данное сообщение", 
                                                                                        props["HostAddress"], 
                                                                                        props["PageAddress"], 
                                                                                        pwdCode,
                                                                                        PageNames.PwdChangeVerificationCode);
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void RoleChange(string login,Guid newRole)
        {
            CheckPermission(Permission.Users, "Нет прав для работы с пользователями");
            using (var db = new DB())
            {
                var user = GetUser(db, login);
                if (user.IsSuperVisor()
                    ||
                    user.IsAdministrator())
                {
                    CheckPermission(Permission.ChangeUserEx, "Нет прав для изменения параметров этой группы пользователей");
                }
                var role = db.Roles.GetOneOrjWorkflowException(newRole,"Роль","");
                user.Role = role;

                LogEvent(db,LogType.UserAction, "Изменена роль пользователя: "+user.Login.Trim()+" =>"+role.Name,null,null,GetUserNameOrExc());
                db.SubmitChanges();
            }
        }


       

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public byte[] GetNoPhotoImage()
        {
            return MainServiceEx.GetNoPhoto();
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public byte[] ConvertImageToPng150x150(byte[] bs)
        {
            if (bs == null) throw new InvalidOperationException();
            try
            {
                return bs.ConvertImageToPng(150, 150);
            }
            catch (Exception)
            {
                jWorkflowException.ThrowNew("Не удалось конвертировать изображение");
                return null;
            }

        }


        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeleteUser(string login)
        {
            CheckPermission(Permission.UserDelete, "Нет прав для удаления пользователей");
            using (var db = new DB())
            {
                var user = GetUser(db, login);
                db.Users.DeleteOnSubmit(user);
                
                  SendMail(user.email,@"Ваш аккаунт {0} удален.", login.Trim());

                db.Messages.DeleteAllOnSubmit(user.Inbox);
                db.Messages.DeleteAllOnSubmit(user.Outbox);

                LogEvent(db,LogType.UserAction, "Удаление пользователя: "+user.Login.Trim(),null,null,GetUserNameOrExc());

                db.SubmitChanges();
            }
        }

        
        
    }
}
